package com.yutel.multher.jas;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.naming.ConfigurationException;

import com.yutel.multher.JasMain;
import com.yutel.multher.jas.comm.ExtensionFileFilter;

public class LifecycleManager {

	private static JasLog log = JasLog.getLogger(JasMain.class);

	protected static final String APPLICATION_HOME_TOKEN = "${application.home}";

	private JasContext config;

	private Object activator;

	public LifecycleManager(JasContext config) {
		this.config = config;
	}

	public void start() throws Exception {
		initActivator();
		startActivator();
	}

	public void stop() throws Exception {
		stopActivator();
	}

	private void initActivator() throws Exception {
		ClassLoader applicationClassLoader = getApplicationClassLoader(config);

		String activatorClassName = config.getProperty("application.activator");
		log.info("activatorClassName=" + activatorClassName);
		if (activatorClassName == null || activatorClassName.length() == 0) {
			activatorClassName = findActivatorByJarService(
					applicationClassLoader, "application.activator");
		}

		if (activatorClassName == null || activatorClassName.length() == 0) {
			throw new IllegalStateException(
					"can't find application activator class");
		}

		try {
			Class<?> activatorClass = applicationClassLoader
					.loadClass(activatorClassName);
			// new an activator object
			activator = activatorClass.newInstance();
		} catch (Exception e) {
			log.error("can't create activator object", e);
			throw e;
		}
	}

	private ClassLoader getApplicationClassLoader(JasContext config)
			throws Exception {
		String classPath = config.getProperty("application.classpath");
		if (classPath == null) {
			// default class path
			classPath = "${application.home}/conf,${application.home}/lib/*.jar";
		}
		log.info("classPath=" + classPath);
		String applicationHome = config
				.getProperty(JasContext.APPLICATION_HOME);
		ClassLoader classLoader = createClassLoader(applicationHome, classPath,
				null);
		if (classLoader == null) {
			classLoader = this.getClass().getClassLoader();
		}

		// set applicationLoader as current thread context class loader
		Thread.currentThread().setContextClassLoader(classLoader);

		return classLoader;
	}

	private ClassLoader createClassLoader(String applicationHome,
			String classPath, ClassLoader parent) throws Exception {
		if ((classPath == null) || (classPath.equals(""))) {
			return parent;
		}

		Set<URL> classPaths = new LinkedHashSet<URL>();
		String[] tokens = classPath.split(",");
		for (String token : tokens) {
			int index = token.indexOf(APPLICATION_HOME_TOKEN);
			if (index == 0) {
				token = applicationHome
						+ token.substring(APPLICATION_HOME_TOKEN.length());
			}

			try {
				URL url = new URL(token);
				classPaths.add(url);
				continue;
			} catch (Exception e) {
			}

			if (token.endsWith("*.jar")) {
				token = token.substring(0, token.length() - "*.jar".length());

				File directory = new File(token);
				if (!directory.exists() || !directory.isDirectory()
						|| !directory.canRead()) {
					continue;
				}

				File[] libs = directory
						.listFiles(new ExtensionFileFilter("jar"));
				for (File jar : libs) {
					log.info("Including glob jar file:" + jar.getAbsolutePath());
					URL url = jar.toURI().toURL();
					classPaths.add(url);
				}

			} else if (token.endsWith(".jar")) {
				File file = new File(token);
				if (!file.exists() || !file.canRead()) {
					continue;
				}

				log.info("Including jar file:" + file.getAbsolutePath());
				URL url = file.toURI().toURL();
				classPaths.add(url);
			} else {
				File directory = new File(token);
				if (!directory.exists() || !directory.isDirectory()
						|| !directory.canRead()) {
					continue;
				}

				log.info("Including directory:" + directory.getAbsolutePath());
				URL url = directory.toURI().toURL();
				classPaths.add(url);
			}
		}

		URL[] urls = classPaths.toArray(new URL[classPaths.size()]);
		if (log.isDebugEnabled()) {
			for (int i = 0; i < urls.length; i++) {
				log.debug("location " + i + " is " + urls[i]);
			}
		}
		for (URL url : urls) {
			log.info("classLoader=" + url);
		}
		ClassLoader classLoader = null;
		if (parent == null) {
			classLoader = new URLClassLoader(urls);
		} else {
			classLoader = new URLClassLoader(urls, parent);
		}
		return classLoader;
	}

	private String findActivatorByJarService(ClassLoader classLoader,
			String activatorId) throws ConfigurationException {
		String serviceId = "META-INF/services/" + activatorId;

		InputStream is = classLoader.getResourceAsStream(serviceId);
		if (is == null) {
			return null;
		}

		BufferedReader rd;
		try {
			rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			rd = new BufferedReader(new InputStreamReader(is));
		}

		String activatorClassName = null;
		try {
			// Does not handle all possible input as specified by the
			// Jar Service Provider specification
			activatorClassName = rd.readLine();
			rd.close();
		} catch (IOException x) {
		}

		return activatorClassName;
	}

	private void startActivator() {
		try {
			long startTime = System.currentTimeMillis();
			Method method = activator.getClass().getMethod("start",
					(Class[]) null);
			method.invoke(activator, (Object[]) null);
			long total = System.currentTimeMillis() - startTime;
			log.info("Activator startup time:" + total);
		} catch (Exception e) {
			log.error("Activator startup failure", e);
			throw new RuntimeException(e);
		}
	}

	private void stopActivator() {
		try {
			long startTime = System.currentTimeMillis();
			Method method = activator.getClass().getMethod("stop",
					(Class[]) null);
			method.invoke(activator, (Object[]) null);
			long total = System.currentTimeMillis() - startTime;
			log.info("Activator shutdown time:" + total);
		} catch (Exception e) {
			log.error("Activator stop error", e);
		}
	}

}
